Stream jest to suma pojedynczych operacji na zbiorze wartości.
Wynikiem może być wartość prymitywna, Obiekt lub wartość void.
(obiekt może być reprezentowany przez obiekt kolekcji takich jak: List,Set,Map itp).
Collection (lub tablica) przechowuje obiekty w pamięci, Stream wywołuje operacje na nich (nie zajmują pamięci).
Zobaczmy jak można wykonać prostą funkcję Stream zastępując blok operacji:
Integer<Integer> liczby = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
int sum = 0;
for(Integer liczba : liczby) {
if ( liczba%2 == 1 ) {
int kwadrat = liczba*liczba;
sum+=kwadrat;
}
}
Cały ten blok można zrobić używając stream:
int sum = liczby.stream()
.filter(liczba -> liczba%2 == 1 ) // przekazuje tylko nieparzyste liczby dalej, odrzuca parzyste.
// przekazane 1,3,5,7,9
.map(liczba -> liczba*liczba) // podnosi do kwadratu i zwraca dalej
// przekazane 1,9,25,49,81
.reduce(0,Integer::sum); // sumuje od 0 + wszystkie elementy
lub to samo obliczenie używając lambdy:
int sum = liczby.stream()
.filter(liczba -> liczba%2 == 1 )
.map(liczba -> liczba*liczba)
.reduce(0, (liczba1,liczba2) -> liczba1 + liczba2);
możemy stworzyć również strumień na podstawie statycznej metody Stream.of,
co daje ten sam efekt.
int suma = Stream.of(liczby)
.filter(liczba -> liczba%2 == 1 )
.map(liczba -> liczba*liczba)
.reduce(0, (liczba1,liczba2) -> liczba1 + liczba2);
Możemy użyć również wzorca projektowego budowniczego do zbudowania strumienia.
Możemy również przekazywać za każdym razem strumień do zmiennej i dopiero go wywoływać.
Stream<Integer> mojStream = Stream.<Integer>builder().add(1).add(2).add(3).add(4).add(5).add(6).add(7).add(8).add(9).add(10).build();
Stream<Integer> nieparzystyStream = mojStream.filter(liczba -> liczba%2 == 1 );
Stream<Integer> kwadratStream = mojSnieparzystyStream.map(liczba -> liczba*liczba);
int suma = kwadratStream.reduce(0,Integer::sum);
możemy to wywołanie zamienić na wielowątkowe, służy do tego zamiast wywołanie stream() to parallelStream().
int sum = liczby.parallelStream()
.filter(liczba -> liczba%2 == 1 )
.map(liczba -> liczba*liczba)
.reduce(0,Integer::sum);
IntStream,LongStream,DoubleStream to strumienie dla danych prymitywnych.
Wszystkie Stream-y (IntStream,LongStream,DoubleStream,Stream<T>) dziedziczą po interface-sie BaseStream,
a on dziedziczy po AutoCloseable.
interface BaseStream<T, S extend BaseStream<T,S>>
i posiada następujące metody:
void close() - zamyka strumień który został wywołany,
tylko niektóre nieliczne strumienie muszą być zamykane.
boolean isParallel() - true jeżeli jest to strumień równoległy. false jeżeli sekwencyjny
Iterator<T> iterator() - zwraca iterator do strumienia.
S onClose(Runnable handler) - zwraca nowy strumień.
handler to zadanie które będzie wywołane kiedy stream jest zamykany.
S parrallel() - na obecnym stream-ie zwracany jest strumień równoległy.
S sequential() - na obecnym stream-ie zwracany jest strumień sekwencyjny.
Spliterator<T> spliterator - pobiera i zwraca referencję Spliterator dla strumienia.
S unordered() - zwraca nieuporządkowany strumień.
Interface Stream posiada nastpujące metody:
<R,A> R collect(Collector<? super T,A,R> collectorFunc) - gromadzi elementy w kontenerze i je zwraca.
T - to typ strumienia,
R - typ strumienia wynikowego,
A - typ elementu gromadzonego w kontenerze.
long count() - ilość elementów w kontenerze.
Stream<T> filter(Predicate<? super T> pred) - zwraca elementy spełniające warunek Predicate
void forEach(Consumer<? super T> aciton) - pętla po wszystkich elementach strumienia
<R> Stream<R> map(Function<? super T, ? extends R> mapFunc) -
stosuje wyrażenie przekazane jako parametr i zwraca nowy strumień
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapFunc) - stosuje wyrażenie przekazane jako parametr zwraca DoubleStream.
IntStream mapToInt(ToIntFunction<? super T> mapFunc) - stosuje wyrażenie przekazane jako parametr zwraca IntStream.
LongStream mapToLong(ToLongFunction<? super T> mapFunc) - stosuje wyrażenie przekazane jako parametr zwraca LongStream.
Optional<T> max(Comparator<? super T> comp) - na podstawie przekazanego komparatora zwraca maksymalny element.
Optional<T> min(Comparator<? super T> comp) - na podstawie przekazanego komparatora zwraca minimalny element.
T reduce(T idenityVal, BinaryOperator<T> accumulator) - pierwszy parametr metody określa początkową inicjalizację elementu, drugi
element wywołują operację dla każdego elementu, taką jak sumowanie wszystkich elementów i zwrócenie wyniku T
Stream<T> sorted() - zwraca strumień posortowany (naturalnie).
Object[] toArray() - zwraca tablicę na podstawie strumienia
IntStream interface posiada dwie metody statycze do generowania zakresu zmiennych:
IntStream range(int start, int end) - zbiór [start,end)
IntStream rangeClosed(int start, int end) - zbiór [start,end]
Stream posiada dwie metody tworzące nieskończone strumienie:
<T> Stream<T> iterate(T seed, UnaryOperator<T> f); - tworzy uporządkowany strumień
<T> Stream<T> generate(Supplier<T> s); - tworzy nieuporządkowany strumień
dla IntStream,LongStream,DoubleStream w zależności od typu danej mamy zamianą zmiennych T na tym prymitywny np:
IntStream iterate(int seed, IntUnaryOperator f );
IntStream generate(IntSupplier<T> s);
Użycie iterate:
Stream<Long> longStream = Stream.iterate(1L, liczba -> liczba + 1);
aby zakończyć nieskończoną iterację możemy stworzyć limit tworzenia strumienia;
możemy zacząć od późniejszego elementu używając metody skip().
Wywołując funkcję forEach możemy wpisać otrzymane elementy;
longStream.skip(50).limit(10).forEach(System.out::println);
Użycie generate:
Stream.generate(Math::random).limit(10).forEach(System.out::println);
z pominięciem 5 pierwszych:
Stream.generate(Math::random).skip(5).limit(10).forEach(System.out::println);
Klasa Random zwraca strumienie typów prymitywnych - ints, longs, doubles:
new Random.ints().skip(5).limit(10).forEach(System.out::println);
Stream.generate(new Random()::nextInt).limit(10).forEach(System.out::println);
Pusty stream tworzymy:
Stream<String> stream = Stream.empty();
IntStream intStream = IntStream.empty();
Strumienie działają na kolekcjach ale również na tablicach:
IntStream intStream =Arrays.stream(new int[]{1,2,3,4,5});
Stream<String> strStream = Arrays.stream({"Mateusz","Michal","Magdalena","Basia","Natalia"});
Lub przez metodę Stream.of:
IntStream intStream = Stream.of(new int[]{1,2,3,4,5});
Stream<String> strStream = Stream.of({"Mateusz","Michal","Magdalena","Basia","Natalia"});
Streamy można również używać na plikach:
try (Stream<String> stream = Files.lines(Paths.get("plikTestowy.txt"))) {
stream.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
Klasa String odziedziczyła po CharSequence metodę chars która tworzy strumień IntStream:
String str = "5 osób miało po 3 zł o 6 godzinie";
str.chars().filter(litera -> !Character.isDigit((char)litera) && !Character.isWhitespace((char)litera))))
.forEach(litera -> System.out.println((char)litera));
Split może podzielić String na Stream po wybranym wyrażeniu regularnym:
Pattern.compile(" ")
.splitAsStream(str)
.forEach(System.out::println);
Zobaczmy na przykład
Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
.map(List::stream)
.forEach( n -> System.out.println(n));
Nie zwróci nam elementów kolekcji tylko adres pamięci:
java.util.stream.ReferencePipeline$Head@6ce253f1
java.util.stream.ReferencePipeline$Head@53d8d10a
w takim wypadku chcemy użyć flatMap, co spowoduje że wszystkie elementy
wprowadzone zostaną do jednego strumienia
Ponieważ nie jest tym razem zwrócony kolejny strumień tylko elementy możemy również
ponownie użyć funkcji map do działania na elementach:
Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
.flatMap(List::stream)
.map( n -> n + 2)
.forEach( n -> System.out.print(n)); // wypisane zostanie: 3456
kolejny przykład:
System.out.print(Stream.of("Mateusz", "Michal", "Magdalena", "Basia", "Natalia")
.flatMap(name -> IntStream.range(0, name.length())
.mapToObj(name::charAt))
.filter(ch -> ch == 'a' || ch == 'A')
.count()
); // wypisana zostanie liczba 10
Przykład jak generować nową listę:
List<Integer> kolekcja = Stream.of(asList(1, 2), asList(3, 4)) // Stream of List<Integer>
.flatMap(List::stream) // map zwróciłoby dwa obiekty stream
.map(integer -> integer + 1)
.collect(Collectors.toList()); // obiekty przekazane są do listy
posortowana lista:
List<Integer> kolekcja = Stream.of(asList(1, 2), asList(1, 2, 3, 4)) // Stream of List<Integer>
.flatMap(List::stream) // map zwróciłoby dwa obiekty stream
.map(integer -> integer + 1)
.sorted() // sortowanie
.collect(Collectors.toList()); // obiekty przekazane są do listy
System.out.println(kolekcja); // [2, 2, 3, 3, 4, 5]
albo kolekcję niepowtarzających się elementów Set:
List<Integer> kolekcja = Stream.of(asList(1, 2), asList(1, 2, 3, 4)) // Stream of List<Integer>
.flatMap(List::stream) // map zwr�ci�oby dwa obiekty stream
.map(integer -> integer + 1)
.collect(Collectors.toSet()); // obiekty przekazane s� do listy
Dla klasy String:
List<String> startM = Stream.of("Mateusz", "Michal", "Magdalena", "Basia", "Natalia")
.filter(p -> p.startsWith("M"))
.collect(Collectors.toList());
System.out.println(startM); // [Mateusz, Michal, Magdalena]
Kolekcja posortowana bez powtarzania się elementów:
SortedSet<String> startM = Stream.of("Mateusz", "Michal", "Magdalena", "Basia", "Natalia")
.filter(p -> p.startsWith("M"))
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(startM); // [Magdalena, Mateusz, Michal]
Optional został stworzony aby nie ały nullpointer-y w programach:
Optional<String> opt = Stream.of("Mateusz", "Michal", "Magdalena", "Basia", "Natalia")
.max((n1, n2) -> n1.compareTo(n2));
if ( opt.isPresent() ) { // sprawdza czy wartość istnieje
System.out.print("Maksymalna wartość to:" + opt.get());
} else {
System.out.print("Nie znaleziono wartości");
}
opt.get() - pobiera wartość z Optional-a.
Klasa optional posiada trzy metody statyczne do stworzenia obiektu:
<T> Optional<T> empty() - tworzy pusty objekt
<T> Optional<T> of(T value) - przekazuje wartość do obiektu, jeżeli value = null zgłaszany
wyjątek NullPointerException.
<T> OptionalT> ofNullable(T value) - przekazuje wartość do obiektu, jeżeli value = null,
tworzony jest pusty Optional
i inne metody do jego obsługi:
T get() - zwraca wartość obiektu Optional
T orElse(T defaultValue) - zwraca wartość obiektu Optional, jeżeli jest pusty to defaultValue:
Foo x = opt.orElse( new Foo() );
T orElseGet(Supplier<? extends T> defaultSupplier) - zwraca wartość obiektu Optional,
jeżeli jest pusty to wyrażenie defaultSupplier:
Foo y = opt.orElseGet( Foo::new );
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
throws X extends Throwable - zwraca wartość obiektu Optional, jeżeli jest pusty to zwraca wybrany wyjątek:
Foo y = opt.orElseThrow(Exception::new);
Dane statystyczne:
class Produkt {
private double cena;
public Produkt(double cena) {
this.cena = cena;
}
public double getCena() {
return cena;
}
}
public class StaticClass {
public static void main(String[] args) {
List<Produkt> produkty = new ArrayList<>();
produkty.add(new Produkt(15));
produkty.add(new Produkt(5.5));
produkty.add(new Produkt(3));
produkty.add(new Produkt(1));
DoubleSummaryStatistics stats = produkty.stream().collect(
Collectors.summarizingDouble(Produkt::getCena));
System.out.println("Liczba elementów:" + stats.getCount()); // 4
System.out.println("Średnia:" + stats.getAverage()); // 6.125
System.out.println("Najwiekszy element:" + stats.getMax()); // 15
System.out.println("Najmniejszy element:" + stats.getMin()); // 1
System.out.println("Suma:" + stats.getSum()); // 24.5
}
}
Do tworzenie map na podstawie metod używamy (java.util.stream.Collectors):
static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper)
class User {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
public class ObjectToMap {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User(1, "Mateusz"));
users.add(new User(2, "Magdalena"));
users.add(new User(3, "Mateusz"));
users.add(new User(4, "Mateusz"));
users.add(new User(5, "Michal"));
Map<Integer, String> idToNameMap = users
.stream()
.collect(Collectors.toMap(User::getId, User::getName));
System.out.println(idToNameMap);
// {1=Mateusz, 2=Magdalena, 3=Mateusz, 4=Mateusz, 5=Michal}
}
}
Ale spróbujmy zrobić mapowanie w drugą stronę, gdzie klucz jest dublowany,
czyli name jako klucz, id jako wartość:
public class ObjectToMap2 {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User(1, "Mateusz"));
users.add(new User(2, "Magdalena"));
users.add(new User(3, "Mateusz"));
users.add(new User(4, "Mateusz"));
users.add(new User(5, "Michal"));
Map<String, Integer> idToNameMap = users
.stream()
.collect(Collectors.toMap(User::getName,User::getId));
System.out.println(idToNameMap);
}
}
dostajemy wyjątek:
Exception in thread "main" java.lang.IllegalStateException: Duplicate key 1
w takim wypadku musimy użyć drugiego wywołania dla tworzenia map,
z uwzględnieniem konfliktów:
static <T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,
Function<? super T,? extends U> valueMapper,
BinaryOperator<U> mergeFunction)
w naszym przykładzie zamienimy id na String aby pokazać jak kleić wartości:
class User {
private String id;
private String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
public class JavaApplicationTest {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User("1", "Mateusz"));
users.add(new User("2", "Magdalena"));
users.add(new User("3", "Mateusz"));
users.add(new User("4", "Mateusz"));
users.add(new User("5", "Michal"));
Map<String, String> idToNameMap = users
.stream()
.collect(Collectors.toMap(User::getName,User::getId, (oldValue,newValue) -> String.join(", ", oldValue, newValue)));
System.out.println(idToNameMap);
//{Michal=5, Magdalena=2, Mateusz=1, 3, 4}
}
}
Wyrażenie (oldValue,newValue) -> String.join(", ", oldValue, newValue) jest wywołane tylko gdy są te same klucze.
Dla pierwszego konfliktu dla klucza Mateusz wartość w mapie jest "1" (oldValue) i wartość z którą jest konflikt to 3.
Zrobiony jest z tego join oddzielony przecinkiem. Czyli "1, 3" i to jest teraz przechowywane w mapie.
Dla kolejnego konfliktu klucza "Mateusz" w mapie przechowywane jest "1, 3" (oldValue) i "4" jako (newValue),
połączone to jest w jedną wartość "1, 3, 4".
Możemy użyć mapowania do liczenia elementów powtarzających się:
public class JavaApplicationTest {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User("1", "Mateusz"));
users.add(new User("2", "Magdalena"));
users.add(new User("3", "Mateusz"));
users.add(new User("4", "Mateusz"));
users.add(new User("5", "Michal"));
Map<String, Integer> idToNameMap = users
.stream()
.collect(Collectors.toMap(User::getName, (count) -> 1, (oldValue,newValue) -> ++oldValue));
System.out.println(idToNameMap);
// {Michal=1, Magdalena=1, Mateusz=3}
}
}
Collectors.joining służy do zwracania z kolekcji jednego stringa połączonego w wartości:
joining():
połączone jeden String do drugiego:
public class JavaApplicationTest {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User("1", "Mateusz"));
users.add(new User("2", "Magdalena"));
users.add(new User("3", "Mateusz"));
users.add(new User("4", "Mateusz"));
users.add(new User("5", "Michal"));
String nameToString = users
.stream()
.map(User::getName)
.collect(Collectors.joining());
System.out.println(nameToString);
// MateuszMagdalenaMateuszMateuszMichal
}
}
joining(CharSequence delimiter):
połączony jeden String do drugiego rozdzielony seperatorem:
public class JavaApplicationTest {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User("1", "Mateusz"));
users.add(new User("2", "Magdalena"));
users.add(new User("3", "Mateusz"));
users.add(new User("4", "Mateusz"));
users.add(new User("5", "Michal"));
String nameToString = users
.stream()
.map(User::getName)
.collect(Collectors.joining(", "));
System.out.println(nameToString);
// Mateusz, Magdalena, Mateusz, Mateusz, Michal
}
}
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
połaczony jeden String do drugiego rozdzielony seperatorem, zaczęty prefix i zakończony suffix:
public class JavaApplicationTest {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User("1", "Mateusz"));
users.add(new User("2", "Magdalena"));
users.add(new User("3", "Mateusz"));
users.add(new User("4", "Mateusz"));
users.add(new User("5", "Michal"));
String nameToString = users
.stream()
.map(User::getName)
.collect(Collectors.joining(", ", "Start:",":Koniec"));
System.out.println(nameToString);
// Start:Mateusz, Magdalena, Mateusz, Mateusz, Michal:Koniec
}
}
Wyrażenie Collectors.groupingBy:
class Produkt {
private String name;
private double cena;
public Produkt(String name, double cena) {
this.name = name;
this.cena = cena;
}
public String getName() {
return name;
}
public double getCena() {
return cena;
}
@Override
public String toString() {
return name + "(" + cena + ")";
}
}
public class JavaApplicationTest {
public static void main(String[] args) {
List<Produkt> produkty = new ArrayList<>();
produkty.add(new Produkt("Banan",3.99));
produkty.add(new Produkt("Jablko",1.99));
produkty.add(new Produkt("Winogrona",4.99));
produkty.add(new Produkt("Banan",3.99));
produkty.add(new Produkt("Jablko",1.99));
produkty.add(new Produkt("Banan",3.99));
Map<String, List<Produkt>> groupByNameMap
= produkty.stream().collect(Collectors.groupingBy(Produkt::getName));
System.out.println(groupByNameMap);
//{Winogrona=[Winogrona(4.99)],
//Banan=[Banan(3.99), Banan(3.99), Banan(3.99)],
//Jablko=[Jablko(1.99), Jablko(1.99)]}
Map<String, Long> result =
produkty.stream().collect(
Collectors.groupingBy(
Produkt::getName, Collectors.counting()
)
);
System.out.println(result);
//{Winogrona=1, Banan=3, Jablko=2}
Map<String, Double> sum = produkty.stream().collect(
Collectors.groupingBy(Produkt::getName, Collectors.summingDouble(Produkt::getCena)));
System.out.println(sum);
// {Winogrona=4.99, Banan=11.97, Jablko=3.98}
Map<Double, Set<String>> resultSet
= produkty.stream().collect(
Collectors.groupingBy(Produkt::getCena,
Collectors.mapping(Produkt::getName, Collectors.toSet())
)
);
System.out.println(resultSet);
// {1.99=[Jablko], 4.99=[Winogrona], 3.99=[Banan]}
}
}
collect(Collectors.groupingBy(Produkt::getName)) - grupuje po Produkt::getName, dodaje do listy ten same obiekt czyli Produkt.
Collectors.groupingBy(Produkt::getName, Collectors.counting()); - wylicza wystąpienia tego samego Produkt::getName.
Collectors.groupingBy(Produkt::getName, Collectors.summingDouble(Produkt::getCena)) - sumuje te same Produkt::getName
Collectors.groupingBy(Produkt::getCena,
Collectors.mapping(Produkt::getName, Collectors.toSet()) - grupowanie po cenie, nie powtarza się nazwa,
ale gdybyśmy dodali produkty.add(new Produkt("Arbuz", 3.99));
to byłoby: {1.99=[Jablko], 4.99=[Winogrona], 3.99=[Banan, Arbuz]}
Mapowanie na true i false używając Collectors.partitioningBy:
static <T> Collector<T,?,Map<Boolean,List<T>>> partitioningBy(Predicate<? super T> predicate)
wybierając warunek logiczny określamy jakie elementy trafią do tablicy false i true:
public class JavaApplicationTest {
public static void main(String[] args) {
List<Produkt> produkty = new ArrayList<>();
produkty.add(new Produkt("Banan", 3.99));
produkty.add(new Produkt("Jablko", 1.99));
produkty.add(new Produkt("Winogrona", 4.99));
produkty.add(new Produkt("Banan", 3.99));
produkty.add(new Produkt("Jablko", 1.99));
produkty.add(new Produkt("Arbuz", 3.99));
Map<Boolean, List<Produkt>> wiecejNiz2 = produkty.stream()
.collect(Collectors.partitioningBy(s -> s.getCena() > 2));
System.out.println(wiecejNiz2);
//{false=[Jablko(1.99), Jablko(1.99)],
//true=[Banan(3.99), Winogrona(4.99), Banan(3.99), Arbuz(3.99)]}
}
}
kolejne wyrażenie może nam rzutować dane pole np na jedną nazwę używając łączenia String-u.
static <T,D,A> Collector<T,?,Map<Boolean,D>>
partitioningBy(Predicate<? super T> predicate, Collector<?
super T,A,D> downstream)
public class JavaApplicationTest {
public static void main(String[] args) {
List<Produkt> produkty = new ArrayList<>();
produkty.add(new Produkt("Banan", 3.99));
produkty.add(new Produkt("Jablko", 1.99));
produkty.add(new Produkt("Winogrona", 4.99));
produkty.add(new Produkt("Banan", 3.99));
produkty.add(new Produkt("Jablko", 1.99));
produkty.add(new Produkt("Arbuz", 3.99));
Map<Boolean, String> wiecejNiz2Name = produkty.stream()
.collect(Collectors.partitioningBy(s -> s.getCena() > 2,
Collectors.mapping(Produkt::getName, Collectors.joining(", "))));
System.out.println(wiecejNiz2Name);
//{false=Jablko, Jablko, true=Banan, Winogrona, Banan, Arbuz}
}
}
collectingAndThen wykonuje dodatkową zmianą stream-u i zwraca wartość.
public class JavaApplicationTest {
public static void main(String[] args) {
List<Produkt> produkty = new ArrayList<>();
produkty.add(new Produkt("Banan", 3.99));
produkty.add(new Produkt("Jablko", 1.99));
produkty.add(new Produkt("Winogrona", 4.99));
produkty.add(new Produkt("Banan", 3.99));
produkty.add(new Produkt("Jablko", 1.99));
produkty.add(new Produkt("Arbuz", 3.99));
String najdrozsze = produkty.stream().collect(
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparing(Produkt::getCena)),
(Optional<Produkt> emp) ->
emp.isPresent() ? emp.get().getName() + "(" + emp.get().getCena() + ")"
: "none"));
System.out.println("Najdroższy produkt to: " + najdrozsze); //Winogrona(4.99)
}
}
Stream API udostępnia kilka operacji znajdowania i dopasowania elementów.
boolean allMatch(Predicate<? super T> predicate) - true jeśli wszystko pasuje
boolean anyMatch(Predicate<? super T> predicate) - true jeśli co najmniej jeden pasuje
boolean noneMatch(Predicate<? super T> predicate) - true jeśli żaden nie pasuje
Optional<T> findAny() - zwraca jakikolwiek element ze strumienia
Optional<T> findFirst() - zwraca pierwszy element ze strumienia
public class JavaApplicationTest {
public static void main(String[] args) {
List<Produkt> produkty = new ArrayList<>();
produkty.add(new Produkt("Banan", 3.99));
produkty.add(new Produkt("Jablko", 1.99));
produkty.add(new Produkt("Winogrona", 4.99));
produkty.add(new Produkt("Banan", 3.99));
produkty.add(new Produkt("Jablko", 1.99));
produkty.add(new Produkt("Arbuz", 3.99));
boolean cenaPonizej4 = produkty.stream()
.allMatch((p) -> p.getCena() < 4);
System.out.println("Ceny poniżej 4zł: " + cenaPonizej4); // false
boolean cenaPonizej6 = produkty.stream()
.allMatch((p) -> p.getCena() < 6);
System.out.println("Ceny poniżej 6zł: " + cenaPonizej6); // true
boolean cenaPonizej2
= produkty.stream()
.anyMatch((p) -> p.getCena() < 2);
System.out.println("Istnieje cena ponizej 2:" + cenaPonizej2); // true
boolean cenaPonizej1
= produkty.stream()
.noneMatch((p) -> p.getCena() < 1);
System.out.println("Nie istnieje cena ponizej 1:" + cenaPonizej1); // true
Optional<Produkt> cenaPonizej2Opt = produkty.stream()
.filter((p) -> p.getCena() < 2)
.findAny();
if (cenaPonizej2Opt.isPresent()) {
System.out.println("Cena ponizej 2 to: " + cenaPonizej2Opt.get()); // Jablko(1.99)
} else {
System.out.println("Nie ma ceny ponizej 2");
}
Optional<Produkt> cenaPonizej2Optfirst = produkty.stream()
.filter((p) -> p.getCena() < 2)
.findFirst();
if (cenaPonizej2Optfirst.isPresent()) {
System.out.println("Cena ponizej 2 to: " + cenaPonizej2Optfirst.get()); //Jablko(1.99)
} else {
System.out.println("Nie ma ceny ponizej 2");
}
}
}
|